#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <netdb.h>
#include <string.h>
#include <errno.h>
#include "imbus_internal.h"
#include "im_address_unix.h"
#include "im_string.h"
#include "im_object_array.h"

#if defined(sun) && !defined(SUN_LEN)
#define SUN_LEN(su) (sizeof(*(su)) - sizeof((su)->sun_path) + strlen((su)->sun_path))
#endif

struct _IMAddressUnix
{
    IMAddress          parent_object;
    IMAddressUnixType  type;
    IMString          *address;
    struct sockaddr   *data;
};

struct _IMAddressUnixClass
{
    IMAddressClass    parent_class;
};

static const IMObjectClass *__im_address_unix_parent_class = 0;

static struct in_addr
__gethostname (const char *host)
{
    struct in_addr addr = { 0 };

#if HAVE_GETHOSTBYNAME_R
    struct hostent hostbuf, *hp;
    size_t hstbuflen;
    char *tmphstbuf;
    int res;
    int herr;

    hstbuflen = 1024;
    /* Allocate buffer, remember to free it to avoid memory leakage.  */
    tmphstbuf = (char*) im_malloc (hstbuflen);

    while ((res = gethostbyname_r (host, &hostbuf, tmphstbuf, hstbuflen, &hp, &herr)) == ERANGE) {
        /* Enlarge the buffer.  */
        hstbuflen *= 2;
        tmphstbuf = (char*) im_realloc (tmphstbuf, hstbuflen);
    }

    /* Found the host */
    if (!res && hp) {
        addr = * ((struct in_addr *)hp->h_addr_list [0]);
    }

    im_free (tmphstbuf);
#else
    struct hostent *hostinfo;

    hostinfo = gethostbyname (host);

    if (hostinfo)
        addr = * ((struct in_addr *) hostinfo->h_addr_list [0]);
#endif
    return addr;
}

static void
__im_address_unix_clear (IMAddressUnix *address)
{
    address->type = IM_ADDRESS_UNIX_INVALID;
    im_string_clear (address->address);
    if (address->data)
        im_free (address->data);
    address->data = 0;
}

static IMBool
__im_address_unix_set (IMAddress *address, const char *addr_string)
{
    IMAddressUnix *p = IM_ADDRESS_UNIX(address);
    IMString *tmp_str;
    IMObjectArray *tmp_arr;
    IMAddressUnixType type = IM_ADDRESS_UNIX_INVALID;
    _im_assert (p);

    if (!addr_string || !*addr_string)
        return FALSE;

    tmp_str = im_string_new_static(addr_string);
    _im_assert(tmp_str);
    tmp_arr = im_string_split(tmp_str, ":");
    im_object_unref(tmp_str);
    _im_assert(tmp_arr);

    if (im_object_array_size(tmp_arr) < 2) {
        im_object_unref(tmp_arr);
        return FALSE;
    }

    // Check address type.
    tmp_str = im_object_array_get(tmp_arr, 0);
    if (im_string_compare_str(tmp_str, "local") == 0 ||
        im_string_compare_str(tmp_str, "unix") == 0 ||
        im_string_compare_str(tmp_str, "file") == 0)
        type = IM_ADDRESS_UNIX_LOCAL;
    if (im_string_compare_str(tmp_str, "tcp") == 0 ||
        im_string_compare_str(tmp_str, "inet") == 0)
        type = IM_ADDRESS_UNIX_INET;

    if (type == IM_ADDRESS_UNIX_INVALID ||
        (type == IM_ADDRESS_UNIX_LOCAL && im_object_array_size(tmp_arr) != 2) ||
        (type == IM_ADDRESS_UNIX_INET && im_object_array_size(tmp_arr) != 3)) {
        im_object_unref(tmp_arr);
        return FALSE;
    }

    // Get address.
    tmp_str = im_object_array_get(tmp_arr, 1);
    if (type == IM_ADDRESS_UNIX_LOCAL) {
        struct sockaddr_un *un = im_new0(struct sockaddr_un, 1);
        un->sun_family = AF_UNIX;
        strncpy(un->sun_path, im_string_c_str(tmp_str), sizeof(un->sun_path));
        un->sun_path[sizeof(un->sun_path) - 1] = '\0';
        __im_address_unix_clear(p);
        p->data = (struct sockaddr*)un;
    } else {
        IMString *tmp_str2 = im_object_array_get(tmp_arr, 2);
        struct sockaddr_in *in = im_new0(struct sockaddr_in, 1);
        in->sin_addr = __gethostname(im_string_c_str(tmp_str));

        if (in->sin_addr.s_addr) {
            in->sin_family = AF_INET;
            in->sin_port = htons(atoi(im_string_c_str(tmp_str2)));
            __im_address_unix_clear(p);
            p->data = (struct sockaddr*)in;
        } else {
            im_free(in);
            im_object_unref(tmp_arr);
            return FALSE;
        }
    }
    im_string_assign(p->address, addr_string, -1);
    p->type = type;
    im_object_unref(tmp_arr);
    return TRUE;
}

static const char*
__im_address_unix_get (const IMAddress *address)
{
    const IMAddressUnix *p = IM_CONST_ADDRESS_UNIX(address);
    _im_assert (p);
    return im_string_c_str(p->address);
}

static const char*
__im_address_unix_get_type_name (const IMAddress *address)
{
    const IMAddressUnix *p = IM_CONST_ADDRESS_UNIX(address);
    _im_assert (p);
    if (p->type == IM_ADDRESS_UNIX_INET)
        return "inet";
    if (p->type == IM_ADDRESS_UNIX_LOCAL)
        return "local";
    return "invalid";
}

static IMBool
__im_address_unix_valid (const IMAddress *address)
{
    const IMAddressUnix *p = IM_CONST_ADDRESS_UNIX(address);
    _im_assert (p);
    return p->type != IM_ADDRESS_UNIX_INVALID;
}

static IMBool
__im_address_unix_serialize (IMConstPointer obj, IMPointer stream)
{
    const IMAddressUnix *ap = IM_CONST_ADDRESS_UNIX(obj);
    IMStream            *sp = IM_STREAM (stream);

    _im_assert (ap && sp);

    /* Call parent serialize method */
    if (!__im_address_unix_parent_class->serialize(obj, stream))
        return FALSE;

    return im_stream_put_c_string(sp, im_string_c_str(ap->address),
                                  im_string_length(ap->address));
}

static IMBool
__im_address_unix_deserialize (IMPointer obj, IMPointer stream)
{
    IMAddressUnix *ap = IM_ADDRESS_UNIX(obj);
    IMStream      *sp = IM_STREAM (stream);
    IMSize len;
    IMBool ret = FALSE;

    _im_assert (ap && sp);

    __im_address_unix_clear (ap);
    /* Call parent deserialize method */
    if (!__im_address_unix_parent_class->deserialize(obj, stream))
        return FALSE;

    if (im_stream_get_data_type(sp) != IM_TYPE_C_STRING)
        return FALSE;

    len = im_stream_get_data_size(sp);
    if (len) {
        IMChar *buf = im_malloc(len+1);
        if (im_stream_get_c_string(sp, buf, len+1))
            ret = __im_address_unix_set((IMAddress*)ap, buf);
        im_free(buf);
    }
    return ret;
}

static void
__im_address_unix_copy (IMPointer dest, IMConstPointer src)
{
    IMAddressUnix *dp = IM_ADDRESS_UNIX(dest);
    const IMAddressUnix *sp = IM_CONST_ADDRESS_UNIX(src);
    _im_assert (dp && sp);

    __im_address_unix_clear(dp);
    __im_address_unix_set((IMAddress*)dp,
                          __im_address_unix_get ((const IMAddress*)sp));
}

static void
__im_address_unix_class_init (IMPointer klass)
{
    IMObjectClass  *ocp = (IMObjectClass *)klass;
    IMAddressClass *acp = (IMAddressClass *)klass;

    ocp->copy           = __im_address_unix_copy;
    ocp->serialize      = __im_address_unix_serialize;
    ocp->deserialize    = __im_address_unix_deserialize;

    acp->set            = __im_address_unix_set;
    acp->get            = __im_address_unix_get;
    acp->get_type_name  = __im_address_unix_get_type_name;
    acp->valid          = __im_address_unix_valid;

    __im_address_unix_parent_class = im_object_class_get_parent (ocp);
}

static void
__im_address_unix_initialize (IMPointer obj)
{
    IMAddressUnix *p = (IMAddressUnix *)obj;
    p->type = IM_ADDRESS_UNIX_INVALID;
    p->address = im_object_ref(im_string_new (0));
    p->data = 0;
}

static void
__im_address_unix_finalize (IMPointer obj)
{
    IMAddressUnix *p = (IMAddressUnix*)obj;
    im_object_unref(p->address);
    if (p->data) im_free(p->data);
}

void
__im_type_register_address_unix ()
{
    static IMTypeInfo im_address_unix_type_info =
    {
        sizeof (IMAddressUnixClass),    /**< class_size >**/
        __im_address_unix_class_init,   /**< class_init >**/
        0,                              /**< class_finalize >**/

        sizeof (IMAddressUnix),         /**< instance_size >**/
        __im_address_unix_initialize,   /**< instance_init >**/
        __im_address_unix_finalize,     /**< instance_finalize >**/
    };

    __im_type_register_builtin_class (IM_TYPE_ADDRESS,
                                      IM_TYPE_ADDRESS_UNIX,
                                      "AddressUnix",
                                      &im_address_unix_type_info,
                                      TRUE);
}

IMAddress*
im_address_unix_new (const char *addr_string)
{
    IMAddress *addr = (IMAddress *)im_object_new(IM_TYPE_ADDRESS_UNIX);
    _im_assert(addr);

    if (addr_string)
        __im_address_unix_set(addr, addr_string);

    return addr;
}

IMAddressUnixType
im_address_unix_get_type (const IMAddressUnix *address)
{
    _im_return_val_if_fail(IM_IS_ADDRESS_UNIX(address),
                           IM_ADDRESS_UNIX_INVALID);
    return address->type;
}

IMConstPointer
im_address_unix_get_data (const IMAddressUnix *address)
{
    _im_return_val_if_fail(IM_IS_ADDRESS_UNIX(address), 0);
    return address->data;
}

IMSize
im_address_unix_get_data_length (const IMAddressUnix *address)
{
    _im_return_val_if_fail(IM_IS_ADDRESS_UNIX(address), 0);
    if (address->type == IM_ADDRESS_UNIX_LOCAL)
        return SUN_LEN((struct sockaddr_un*)address->data);
    else if (address->type == IM_ADDRESS_UNIX_INET)
        return sizeof(struct sockaddr_in);
    return 0;
}

IMBool
im_address_unix_remove_related_file (const IMAddressUnix *address)
{
    _im_return_val_if_fail(IM_IS_ADDRESS_UNIX(address), 0);
    if (address->type == IM_ADDRESS_UNIX_LOCAL) {
        struct sockaddr_un *un = (struct sockaddr_un*)address->data;
        return unlink(un->sun_path) == 0;
    }
    return TRUE;
}

IMBool
im_address_unix_secure_related_file (const IMAddressUnix *address)
{
    _im_return_val_if_fail(IM_IS_ADDRESS_UNIX(address), 0);
    if (address->type == IM_ADDRESS_UNIX_LOCAL) {
        struct sockaddr_un *un = (struct sockaddr_un*)address->data;
        return chmod(un->sun_path, S_IRUSR | S_IWUSR) == 0;
    }
    return TRUE;
}

/*
vi:ts=4:nowrap:ai:expandtab
*/
